package binary

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"io"
	"reflect"
	"strconv"
	"strings"

	"github.com/pkg/errors"
)

//用于在翻译过程中进行一些相关的配置工作，例如在某些底层物理网络中使用发送地址作为接收地址，或者忽略掉为某个地址配置的错误忽略等
type config interface {
	PullOutAddr() []byte
	ShouldOmit(string, error) bool
}

func Marshal(addressBits []byte, v interface{}) ([]byte, error) {
	rv := reflect.ValueOf(v)
	rt := reflect.TypeOf(v)

	if rt.Kind() == reflect.Ptr {
		rv = rv.Elem()
		rt = rt.Elem()
	}

	var res []byte
	var start = 0
	var lastFn func()

	for index := 0; index < rt.NumField(); index++ {
		fieldT := rt.Field(index)
		tag := ParseTag(rt.Field(index).Tag.Get(TAGMARK))
		if bit := tag.GetBytes("startbit"); bit != nil { //|| tag.Has("endbit")
			res = append(res, bit...)
			if !rv.Field(index).CanSet() {
				start++
			}
		} else if bit := tag.GetBytes("endbit"); bit != nil { //|| tag.Has("endbit")
			res = append(res, bit...)
			if !rv.Field(index).CanSet() {
				start++
			}
		} else if tag.Has("crc16") {
			crc := Crc16(res[start:])
			b := make([]byte, 2)
			tag.Endian().PutUint16(b, crc)
			res = append(res, b...)
		} else if bts := tag.GetAddrBytes(addressBits); bts != nil {
			res = append(res, bts...)
		} else if tag.Has("length") {
			var length int
			var lengthNum = index
			var kind reflect.Kind
			kind = fieldT.Type.Kind()
			lastFn = func() {
				var bytes []byte
				length = len(res)
				switch kind {
				case reflect.Uint8:
					bytes = []byte{byte(length + 1)}
				case reflect.Uint16:
					bytes = make([]byte, 2)
					binary.BigEndian.PutUint16(bytes, uint16(length+2))
				}
				tail := res[lengthNum:]
				var resHeader = make([]byte, lengthNum)
				copy(resHeader, res[:lengthNum])
				resHeader = append(resHeader, bytes...)
				res = append(resHeader, tail...)
			}
		} else {
			if bts := tag.GetFixedBytes(); bts != nil { //固定值
				res = append(res, bts...)
			} else {
				//read data from v
				switch fieldT.Type.Kind() {
				case reflect.Uint8:
					res = append(res, byte(rv.Field(index).Uint()))
				case reflect.Slice:
					if fieldT.Type.Elem().Kind() == reflect.Uint8 {
						res = append(res, rv.Field(index).Interface().([]byte)...)
					}
				case reflect.String:
					var length = tag.GetInt("length")
					var bts = []byte(rv.Field(index).String())
					if length > 0 {
						bts = bts[:length]
					}
					res = append(res, bts...)
				}
			}
		}
	}
	if lastFn != nil {
		lastFn()
	}
	return res, nil
}

func Unmarshal(r *Buffer, m config, msg interface{}) (addrString string, values map[string]interface{}, err error) {
	rv := reflect.ValueOf(msg)
	resultV := rv
	rt := reflect.TypeOf(msg)

	var addrBts []byte
	var shouldOmitError string
	// var setaddrByPullOut = false

	if rt.Kind() == reflect.Ptr {
		rv = rv.Elem()
		rt = rt.Elem()
	} else {
		err = errors.New("need pointer")
		return
	}
	newV := resultV.Elem()

	var i, start = 0, 0

	var valuesArr []interface{}

	var length, fixedLength int

	for index := 0; index < rt.NumField(); index++ {
		fieldT := rt.Field(index)
		switch fieldT.Type.Kind() {
		case reflect.Uint8:
			fixedLength++
		case reflect.Uint16:
			fixedLength = fixedLength + 2
		case reflect.Array:
			fixedLength = fixedLength + fieldT.Type.Len()
		case reflect.String:
			t := rt.Field(index).Tag.Get(TAGMARK)
			if t == "" {
				t = strings.ToLower(fieldT.Name)
			}
			tag := ParseTag(t)
			var singleLen = tag.GetInt("length")
			if singleLen > 0 {
				fixedLength += singleLen
			}
		}
	}

	for index := 0; index < rt.NumField(); index++ {
		fieldT := rt.Field(index)
		t := rt.Field(index).Tag.Get(TAGMARK)
		if t == "" {
			t = strings.ToLower(fieldT.Name)
		}
		tag := ParseTag(t)

		if tag.Has("as-pull-out") {
			addrBts = m.PullOutAddr()
			addrString = "0x" + hex.EncodeToString(addrBts)
			// setaddrByPullOut = true
			continue
		}

		var additionalSet func(interface{})

		if tag.Has("crc16") {
			var bts = make([]byte, 2)
			var n int
			if n, err = r.Read(bts); err == nil {
				if n == 2 {
					tested := r.Seek(start, i)
					crc := Crc16(tested)
					var got = tag.Endian().Uint16(bts)
					if got != crc {
						err = NewError(ErrorWrongCrc, fmt.Sprintf("crc should be (%0x), but actual is(% x)\n", crc, bts))
						if m.ShouldOmit(addrString, err) {
							err = nil
							continue
						}
					} else {
						continue
					}
				} else {
					err = fmt.Errorf("check crc but has no more 2-bytes data")
				}
				return
			}
		}

		if tag.Has("sum") {
			var bts = make([]byte, 1)
			var n int
			if n, err = r.Read(bts); err == nil {
				if n == 1 {
					tested := r.Seek(start, i)
					crc := Sum(tested)
					if crc != bts[0] {
						err = NewError(ErrorWrongCrc, fmt.Sprintf("crc should be (%0x), but actual is(% x)\n", crc, bts))
						if m.ShouldOmit(addrString, err) {
							err = nil
							continue
						}
					} else {
						continue
					}
				} else {
					err = fmt.Errorf("check sum but has no more 1-bytes data")
				}
				return
			}
		}

		if !rv.Field(index).CanSet() {
			switch fieldT.Type.Kind() {
			case reflect.Uint8:
				start++
			case reflect.Uint16:
				start = start + 2
			}
		}

		var readedBytes []byte

		if bit := tag.GetBytes("startbit"); bit != nil { //|| tag.Has("endbit")
			readedBytes = make([]byte, len(bit))
			r.Read(readedBytes)
			if bytes.Compare(readedBytes, bit) != 0 {
				err = fmt.Errorf("Startbit应该为（%x），实际获得（%x）\n", bit, readedBytes)
				return
			} else {
				continue
			}
		} else if bit := tag.GetBytes("endbit"); bit != nil { //|| tag.Has("endbit")
			readedBytes = make([]byte, len(bit))
			r.Read(readedBytes)
			if bytes.Compare(readedBytes, bit) != 0 {
				err = fmt.Errorf("endbit%x），实际获得（%x）\n", bit, readedBytes)
				return
			} else {
				continue
			}
		} else if strings.HasPrefix(t, "address") {
			tags := strings.Split(t, ",")
			if strings.Contains(tags[0], "[") {
				parts := strings.Split(tags[0], "[")
				numbers := strings.Split(strings.TrimSuffix(parts[1], "]"), " ")
				addrSource := make([]byte, len(numbers))
				r.Read(addrSource)
				for j := 0; j < len(numbers); j++ {
					var num int
					if num, err = strconv.Atoi(numbers[j]); err == nil {
						switch fieldT.Type.Kind() {
						case reflect.Uint8:
							if rv.Field(index).CanSet() {
								newV.Field(index).SetUint(uint64(addrSource[num]))
							}
						case reflect.Array:
							newV.Field(index).Field(j).SetUint(uint64(addrSource[num]))
						}
						for num >= len(addrBts) {
							addrBts = append(addrBts, 0)
						}
						// if setaddrByPullOut && addrBts[num] != 0 && addrBts[num] == addrSource[num] {
						// TODO should check
						// }
						addrBts[num] = addrSource[num]
						i++
					} else {
						return
					}
				}
			} else {
				if addrBts, _, err = SetSliceToArray(r, newV.Field(index)); err != nil {
					return
				}
				i = i + newV.Field(index).Len()
			}
			continue
		} else if strings.HasPrefix(t, "optional") {
			shouldOmitError = "EOF"
		} else if lTaq, ok := tag["length"]; ok {
			additionalSet = func(val interface{}) {
				switch tv := val.(type) {
				case uint16:
					length = int(tv)
					if lTaq == "followed" {
						length += fixedLength
					}
					fmt.Println("length is", length)
				}
			}
		}

		isValue := strings.ToLower(fieldT.Name) == "value" || strings.HasPrefix(t, "value")

		fmt.Println(r.Len())

		switch fieldT.Type.Kind() {
		case reflect.Uint8:
			var bts byte
			if readedBytes != nil {
				bts = readedBytes[0]
			} else {
				bts, err = r.ReadByte()
			}
			if err == nil {
				if newV.Field(index).CanSet() {
					newV.Field(index).SetUint(uint64(bts))
					if isValue {
						valuesArr = append(valuesArr, uint64(bts))
					}
				}
			} else if shouldOmitError == "EOF" && err == io.EOF {
				err = nil
				continue
			} else {
				return
			}
			i++
		case reflect.Uint16:
			var bts = make([]byte, 2)
			for index := 0; index < 2; index++ {
				if bts[index], err = r.ReadByte(); err != nil {
					return
				}
			}
			var number = tag.Endian().Uint16(bts)
			if rv.Field(index).CanSet() {
				newV.Field(index).SetUint(uint64(number))
			}
			if isValue {
				valuesArr = append(valuesArr, uint64(number))
			}
			i = i + 2
		case reflect.Array:
			var bts []byte
			var n int
			bts, n, err = SetSliceToArray(r, newV.Field(index))
			i += n
			if err == nil {
				len := newV.Field(index).Len()
				if isValue {
					for index := 0; index < len; index++ {
						valuesArr = append(valuesArr, uint64(bts[index]))
					}
				}
			} else {
				return
			}
		case reflect.Slice:
			bts := make([]byte, length-fixedLength)
			r.Read(bts)
			newV.Field(index).SetBytes(bts)
		case reflect.String:
			var strLength = tag.GetInt("length")
			if strLength == 0 {
				strLength = length - fixedLength
			}
			bts := make([]byte, strLength)
			if _, err = r.Read(bts); err == nil {
				newV.Field(index).SetString(string(bts))
			} else {
				return
			}
		}

		if additionalSet != nil {
			additionalSet(newV.Field(index).Interface())
		}
		// rv.Field(index).Set()
	}

	values = make(map[string]interface{}, len(valuesArr))

	for n, val := range valuesArr {
		values[fmt.Sprintf("v%d", n)] = val
	}
	return
}
